package net.pulseproject.router.app.view;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.WindowConstants;

import net.pulseproject.commons.gui.ActionListenerX;
import net.pulseproject.commons.gui.IconsCache;
import net.pulseproject.commons.misc.binder.Binder;
import net.pulseproject.commons.misc.binder.BooleanBinder;
import net.pulseproject.commons.util.GuiUtil;
import net.pulseproject.router.app.gui.Event;
import net.pulseproject.router.app.gui.EventListener;
import net.pulseproject.router.app.gui.Event.CloseConfigurationEvent;
import net.pulseproject.router.app.gui.Event.ExecuteConfigurationEvent;
import net.pulseproject.router.app.gui.Event.QuitEvent;
import net.pulseproject.router.app.misc.OSXAdapter;
import net.pulseproject.router.app.model.Model;
import net.pulseproject.router.app.model.ModelField;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.google.inject.Inject;

public class MainWindowImpl extends JFrame implements MainWindow, MainMenuBarListener {
	
	private static final Log LOG = LogFactory.getLog(MainWindowImpl.class);
	
	public static final String NAME_CONFIGURATION_TEXT = "ConfigurationTextArea";
	public static final String NAME_ACTIVATE_ROUTING_BUTTON = "ActivateRoutingButton";
	public static final String NAME_CLOSE_ROUTING_BUTTON = "CloseRoutingButton";
	
	private static final long serialVersionUID = -1725749653198188269L;
	private static final Font FONT_CONFIGURATION_TEXT = new Font("Monaco", Font.PLAIN, 11);
	private static final boolean IS_MACOSX = System.getProperty("os.name")
		.toLowerCase(Locale.ENGLISH).startsWith("mac os x");
	
	private final transient Model model;
	
	private final Set<EventListener> listeners = new HashSet<EventListener>();
	
	private final JTextArea configurationText = new JTextArea();
	
	private final transient Binder<ModelField, String> configBinder =
		new ModelTextComponentBinder(ModelField.CONFIGURATION_TEXT, this.configurationText);
	
	private final transient BooleanBinder<ModelField> routingActiveBinder =
		new BooleanBinder<ModelField>(ModelField.ROUTING_ACTIVE) {
		@Override public void onTypedValueSet(final Boolean value) {
			MainWindowImpl.this.onSetRoutingActive(value.booleanValue());
		}
	};
	
	private final JButton btnActivate = new JButton("Activate Routing");
	
	private final JButton btnClose = new JButton("Close Routing");
	
	private final SoloPanel soloPanel;
	
	private PortsWindow portsWindow;
	
	private final PreferencesDialogFactory preferencesDialogFactory;
	
	private final MainMenuBarFactory mainMenuBarFactory;
	
	private final IconsCache iconsCache;
	
	@Inject
	public MainWindowImpl(
			final Model model,
			final PreferencesDialogFactory preferencesDialogFactory,
			final MainMenuBarFactory mainMenuBarFactory,
			final IconsCache iconsCache) {
		super("Pulse Router");
		LOG.debug("new MainWindowImpl(...)");
		
		this.model = model;
		this.preferencesDialogFactory = preferencesDialogFactory;
		this.mainMenuBarFactory = mainMenuBarFactory;
		this.iconsCache = iconsCache;
		
		this.setName(MainWindow.NAME);
		this.soloPanel = new SoloPanel(iconsCache, model, this); // TODO bypass guice ;)
	}

	@Override
	public final void initComponents() {
		LOG.info("initComponents()");
		
		this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
		this.addWindowListener(new WindowAdapter() {
			@Override
			public void windowClosing(final WindowEvent e) {
				MainWindowImpl.this.onQuit();
			}
		});
		
		final MainMenuBar menuBar = this.mainMenuBarFactory.create(this, this, this.model.getVersionString(), IS_MACOSX);
		menuBar.initCompontents();
		this.setJMenuBar(menuBar);
		
		final JPanel panel = this.createMainPanel();
		this.getContentPane().add(panel);
		
		if(IS_MACOSX == true) {
			this.registerOsxListener();
		}
		
		this.registerBindings();
	}
	
	private void registerOsxListener() {
		try {
			OSXAdapter.setQuitHandler(this, this.getClass().getDeclaredMethod("osxQuit", (Class[]) null));
			OSXAdapter.setPreferencesHandler(this,
					this.getClass().getDeclaredMethod("osxPreferences", (Class[]) null));
		} catch (final SecurityException e) {
			throw new RuntimeException("Could not initialize Mac OS X handlers!", e);
		} catch (final NoSuchMethodException e) {
			throw new RuntimeException("Could not initialize Mac OS X handlers!", e);
		}
	}
	
	@Override
	public final void updateWindowSize(final WindowSize size) {
		LOG.trace("updateWindowSize(size=" + size + ")");
		if(size == null) {
			this.pack();
			GuiUtil.setCenterLocation(this);
		} else {
			this.setSize(size.getDimension());
			this.setLocation(size.getLocation());
		}
	}
	
	private void registerBindings() {
		this.model.addBinding(this.configBinder);
		this.model.addBinding(this.routingActiveBinder);
	}
	
	private void unregisterBindings() {
		this.model.removeBinding(this.configBinder);
		this.model.removeBinding(this.routingActiveBinder);
	}
	
	private JPanel createMainPanel() {
		this.configurationText.setName(NAME_CONFIGURATION_TEXT);
//		this.configurationText.setColumns(100);
		this.configurationText.setFont(FONT_CONFIGURATION_TEXT);
		final JScrollPane configurationTextScrollable = new JScrollPane(this.configurationText);
		configurationTextScrollable.setPreferredSize(new Dimension(800, 260));
		
		final JPanel centerPanel = new JPanel();
		centerPanel.setLayout(new BorderLayout());
		centerPanel.add(this.createSouthPanel(), BorderLayout.SOUTH);
		centerPanel.add(configurationTextScrollable, BorderLayout.CENTER);
		
		final JPanel panel = new JPanel();
		panel.setLayout(new BorderLayout());
		panel.add(this.soloPanel, BorderLayout.NORTH);
		panel.add(centerPanel, BorderLayout.CENTER);
		return panel;
	}

	private JPanel createSouthPanel() {
		final JPanel panel = new JPanel();
		
		
		this.btnActivate.setName(NAME_ACTIVATE_ROUTING_BUTTON);
		this.btnActivate.addActionListener(new ActionListenerX(this.iconsCache, this, "Could not activate routing!") {
			@Override
			protected void action(final ActionEvent event) {
				MainWindowImpl.this.onBtnExecuteClicked(event);
			}
		});
		panel.add(this.btnActivate);
		
		this.btnClose.setName(NAME_CLOSE_ROUTING_BUTTON);
		this.btnClose.addActionListener(new ActionListenerX(this.iconsCache, this, "Could not close routing!") {
			@Override protected void action(final ActionEvent event) {
				LOG.info("onBtnCloseClicked(event)");
				
				MainWindowImpl.this.broadcastEvent(new CloseConfigurationEvent());
			}
		});
		panel.add(this.btnClose);
		
		return panel;
	}
	
	private void onBtnExecuteClicked(final ActionEvent event) {
		LOG.info("onBtnExecuteClicked(event)");
		final String rawText = this.configurationText.getText();
		this.broadcastEvent(new ExecuteConfigurationEvent(rawText));
	}

//	final void onTransmitterButtonClicked(final Device device, final int channel) {
//		LOG.info("onTransmitterButtonClicked(device=" + device + ")");
//	}
	
//	private void onReceiverButtonClicked(final Device device, final int channel) {
//		LOG.info("onReceiverButtonClicked(device=" + device + ")");
//		final Integer controller = MidiSocket.IGNORE_CONTROLLER; // TODO controllerNumber
//		final MidiSocket socket = new MidiSocket(device.getPortName(), Integer.valueOf(channel), controller);
//		final boolean yetEnabled = this.wrapper.isEnabledFor(socket);
//		this.wrapper.setEnabledFor(socket, !yetEnabled);
//	}
	
	public final void osxQuit() {
		this.onQuit();
	}

	public final void osxPreferences() {
		this.onPreferences();
	}
	
	@Override
	public final void onPreferences() {
		LOG.debug("onPreferences()");
		
		final PreferencesDialog preferences = this.preferencesDialogFactory.create(this);
		preferences.setVisible(true); // TODO reuse same instance via hide/visible
	}

	private void onSetRoutingActive(final boolean routingActive) {
		this.btnActivate.setEnabled(routingActive == false);
		this.btnClose.setEnabled(routingActive == true);
		this.configurationText.setEditable(routingActive == false);
	}
	
	/**
	 * Implements {@link MainMenuBarListener}.
	 */
	@Override
	public final void onQuit() {
		LOG.debug("onQuit()");
		
		this.unregisterBindings();
		this.model.setConfigurationText(this.configurationText.getText());
		if(this.portsWindow != null) {
			this.portsWindow.dispose(); // otherwise, application wont quit smoothly
		}
		this.broadcastEvent(new QuitEvent());
	}
	
	/**
	 * Implements {@link MainMenuBarListener}.
	 */
	@Override
	public final void onToggleAvailablePortsWindow() {
		LOG.debug("onToggleAvailablePorts()");
		if(this.portsWindow == null) {
			this.portsWindow = new PortsWindow(this.model);
			GuiUtil.setCenterLocation(this.portsWindow);
		}
		this.portsWindow.setVisible(!this.portsWindow.isVisible());
	}

	@Override
	public final void addEventListener(final EventListener listener) {
		this.listeners.add(listener);
	}
	
	private void broadcastEvent(final Event event) {
		for (final EventListener listener : this.listeners) {
			listener.onEventDispatched(event);
		}
	}

}
